3. Klasser och dynamisk minnesallokering.

 

·     Free store.

 

·     Pekaren som klassmedlem.

 

·     Tilldelningsoperatorns effekt på klasser.

 

·     Överlagring av tilldelningsoperatorn.

 

·     this.

 

·     Kopieringskonstruktorn.

·     Objekt som argument/returvärde.

 

·     Referenser till objekt som argument/returvärde.

 

 

 

 

 

 

 

 

 

 


Free store.

 

Det lediga minne som finns tillgängligt för programmet kallas i C för ‘heap’. Man avser då det minne man har tillgång till förutom det som reserverades (allokerades) när programfilen lästes in. I C++ kallar man det lediga minnet ‘the free store’. Skillnaden ligger i vilka funktioner man använder när man allokerar minne.

 

I C använder man sig av funktionerna malloc() och free(), när man vill reservera minne. I C++ använder man sig i stället av de i språket inbyggda instruktioner­na ‘new’ och ‘delete’. Vi har sett dem tidigare, men nu när vi ska börja använda dynamisk minnesallokering för objekt i stället för variabler och strukturer upp­står en väsentlig skillnad mellan de båda metoderna.

 

När man skapar ett objekt ska dess konstruktor anropas. Detta kan inte malloc() göra, men väl ‘new’. Samma sak gäller för destruktorn, ‘delete’ anropar den automatiskt.

 

Om man ändå försöker sig på att använda malloc() förlorar man dataintegritet­en:

 

CCirkel *pC;

pC = (CCirkel*)malloc(sizeof(CCirkel));

cout << pC->GetRadie(); // Skriver ut en odefinierad radie.

pC->VisaYta();  // Skriver ut en odefinierad yta.

 

Hade vi haft möjlighet att skriva ut m_PI skulle vi med största sannolikhet finna att den inte var 3.14159, om inte det råkade vara det som stod i minnet på just den positionen.

 

Lägg även märke till att pekaren kan vara av fel typ. Vi måste ju själva skriva en typomvandlig, och då har vi ju all möjlighet att ‘ljuga’ för kompilatorn. På så sätt förlorar vi alla offset i klassen, d.v.s. vi vet inte var medlemmarna ligger. Dessutom kan vi med eget påhittade offset ställa till med vad som helst i objek­tet, dataintegriteten är förstörd.

 

Om vi däremot använder ‘new’ behöver vi inte typomvandla. Kompilatorn kontrollerar att vi inte använder en klass och en pekare som inte hör ihop:

 

int *pC;

pC = new CCirkel; // Fel: ‘type mismatch’

 

Använd därför alltid ‘new’ när du allokerar minne för klasser.

Vi har redan provat att använda ‘new’ på klassen CCirkel. Låt oss se vad som händer om vi gör detsamma på klassen CDatum. Där har vi ju en överlagrad konstruktor.

 

CDatum *pD1, *pD2;

 

pD1 = new CDatum; // ‘Default constructor’ används!

pD2 = new CDatum(1996, 7, 19);

 

pD1->VisaDatum();

pD2->VisaDatum();

 

Detta borde resultera i följande utskrift:

 

                                

 

Alltså klarar ‘new’ av att anropa rätt konstruktor. Detta fungerar såväl för överlagrade konstruktorer (inklusive den förvalda) som för konstruktorer med förvalda argument. Observera att vi inte angav paranteser när vi använde den förvalda konstruktorn.

 

Du kan råka ut för att minnet inte räcker till när din användare använder ditt program. Då uppstår en situation du inte sett medan du utvecklade det. Det är därför viktigt att kontrollera att Windows verkligen tillät ‘new’ att allokera minne. Om så inte var fallet kan du testa på pekarens innehåll. Om allokeringen misslyckats innehåller pekaren värdet 0. (I C++ innehåller en tom pekare snarare värdet 0 i stället för NULL, som fallet är i C.)

 

Observera att det inte gör något om du ändå gör ‘delete’ på pekaren. Det är full­ständigt riskfritt att använda ‘delete’ på pekare som innehåller värdet 0 i C++. Du kan dock endast använda ‘delete’ på pekare som tilldelats värde av ‘new’, och du får endast göra det en gång!

 

Övningsuppgift:

·      Öppna projektet Datum.

·      Ändra i main() så att du använder pekare enligt ovanstående modell i stället för att skapa namngivna objekt.

·      I slutet av main använder du ‘delete’ för att ta bort objekten.

·      Kompilera, länka och testa. Det ska fungera likadant som innan.


Pekaren som klassmedlem.

 

Det finns naturligtvis ingenting som förbjuder dig att använda ‘new’ i en metod, en medlemsfunktion i en klass. Detta skulle erbjuda möjlighet till dynamisk minnesallokering från en klass. Då skulle detta ske helt transparent för den som använder klassen.

 

Låt oss testa detta genom att skapa en klass som hanterar en text. Meningen är att man ska kunna lagra en text i klassen utan att behöva tänka på textens längd etc. Låt oss kalla klassen CText:

 

#include <iostream.h>

#include <string.h>

 

class CText

{

public:

CText(const char *t);

    void Skriv() const {if(m_pText) cout << m_pText;}

    ~CText();

private:

    char *m_pText;

};

// Konstruktor som tar emot en textpekare:

CText::CText(const char *t)

{

    m_pText = new char[strlen(t) + 1];

    strcpy(m_pText, t);

}

 

// Destruktor

CText:: ~CText()

{

    delete [] m_pText;

}

 

Denna klass klarar av att allokera minne till en text när man skapar ett objekt, att visa texten på skärmen på uppmaning, samt att frigöra minnet när objektet förstörs. Observera syntaxen för att frigöra minne när man avser en lista: ett par tomma hakparanteser före pekarens namn. Om du skriver någon dimension mellan hakparanteserna kommer kompilatorn att ignorera den. Så här kan man använda klassen:

 

void main()

{

    CText Text(”Detta är en text!”);

    CText *pT = new CText(”Denna text når du via en pekare!”);

    Text.Skriv();

    pT->Skriv();

}

Observera att om man har en förvald konstruktor d.v.s en konstruktor som skapar en tom sträng, tilldelar man m_pText värdet noll. Detta är nödvändigt eftersom destruktorn kommer att göra delete på denna pekare va­re sig vi tilldelat den ett värde eller ej. Man får ju inte göra delete på en pekare som inte tilldelats värde, men värdet noll är säkert i samband med delete.

 

Lägg även märke till att vi måste testa pekaren innan vi använder cout i funk­tion­en Skriv(). Tidigare version av cout var säker med nollpekare, men inte den vi fått med version 4.0.

 

Till sist en varning: När man skapar ett objekt och använder den förvalda konstruktorn ska man inte skriva paranteserna efter objektnamnet. Gör man detta kommer nämligen kompilatorn att skapa en ny klass för objektet, vilken inte kommer att innehålla våra metoder och dessa känns då inte igen för det objektet:

 

CText Text1;   // OK, vår konstruktor används.

CText Text2(); // OK, autogenererad konstruktor används.

 

Text1.Skriv(); // OK, vår funktion Skriv() anropas. Den skriver

               // ingenting eftersom m_pText = 0.

 

Text2.Skriv(), // Error, objektet Text2 har ingen funktion som

               // heter Skriv()!

 

 

 

 

 

Övningsuppgift:

·      Skapa ett projekt Text.

·      Deklarera ovanstående klass i samma fil som main().

·      Eftersom du skapat det ena objektet via ‘new’ ska du ta bort det med ‘delete’ när du inte behöver det längre.

·      Testa, utskriften ska bli så här efter att du lagt till lämpliga nyradstecken:

 

                

 


Övningsuppgift:

·      Gör följande tillägg i klassen CText.

·      Lägg till en privat medlem för längden, typ ‘int’, namn ‘m_Langd’.

·      Ändra i konstruktorn så att den sparar längden. (Skriv om det som behövs, var inte så lat att du anropar strlen() två gånger!)

·      Lägg till en accessfunktion GetLangd() som returnerar m_Langd. Den ska precis som Skriv() deklareras i klasshuvudet.

·      Lägg till en förvald konstruktor (d.v.s. en som inte tar några argument alls), vilken lägger in värdet 0 i m_pText och m_Langd.

·      Lägg till en konstruktor som tar emot ett tecken och en längd. Den lagrade texten ska fyllas med m_Langd stycken tecken (t.ex. CText(‘*’ , 5) ska lagras som ”*****”). Glöm inte att du ska allokera minne för m_Langd + 1 tecken!

·      Lägg till en funktion Set(), vilken tar emot ett index och ett tecken. Tecknet ska lagras i texten på den position som index anger om index är större än 0 och mindre eller lika med m_Langd. Observera att första tecken ska räknas som tecken nummer 1!

·      Lägg till en funktion Get(), vilken tar emot ett index och returnerar det tecken som finns på motsvarande position i texten. Observera att första tecken räk­nas som tecken nummer 1! Om indexet pekar utanför texten ska värdet 0 returneras i stället.

·      Gör tillägg i main() för att testa all den nya funktionaliteten. Observera att även en tom text kan skrivas ut utan programkörningsfel. Det händer bara ingenting.

 

Vad ska vi med en tom sträng till om vi inte kan lägga till något i den? Låt oss alltså göra klassen bättre genom att lägga till Append():

 

// Lägger till text:

void CText::Append(const char *a)

{

         char *temp;

 

         // Om det finns text från början:

         if(m_pText)

         {

                 m_Langd += strlen(a);

                 temp = new char [m_Langd + 1];

                 strcpy(temp, m_pText);

                 strcat(temp, a);

                 delete [] m_pText;

                 m_pText = temp;

         }

         // Om det inte finns text från början:

         else

         {

                 int l = strlen(a);

                 m_Langd = l;

                 m_pText = new char[l + 1];

                 strcpy(m_pText, a);

         }

}

 

 

Nu kan vi alltså förlänga texten i våra objekt. Vi kan t.ex. testa på följande sätt:

 

CText ctNamn(”Håkan”);  // ctNamn Innehåller nu ”Håkan”

ctNamn.Append(” Berg”); // ctNamn innehåller nu ”Håkan Berg”

 

Vi har hanterat ‘append’ till tom sträng också. Skulle vi inte gjort det skulle det blivit tillägg på adress 0 i minnet, vilket ger ett skyddsfel. Som det ser ut nu kan vi skriva så här:

 

CText ctTom; // Tomt objekt.

ctTom.Append(”Detta lägger vi till!”);

 

Detta är alltså ett exempel på en klass som dynamiskt allokerar minne, och därför måste ha en destruktor. När programmet går ur det sammanhang där objektet ingår kommer objektets variabler m_Langd och m_pText automatiskt att försvinna, men inte det minne som m_pText pekar på. Det måste vi därför ta hand om i destruktorn, vilket vi gjorde redan från början. Se den ursprungliga deklarationen ovan.

 

Övningsuppgift:

·      Testa detta genom att lägga till Append() i klassen CText och sätt ihop ditt för och efternamn p.s.s. som ovan.

·      Prova effekten av att skapa en tom text och sedan fylla i den m.h.a. Append().


Tilldelningsoperatorns effekt på klasser.

 

Som vi tidigare sett kan man inte använda tilldelningsoperatorn för att tilldela en lista ett värde. Det är ju därför vi behöver trixa så med texter. Däremot har jag inte talat om att man faktiskt kan tilldela en hel struktur värdet av en annan struktur.

 

Vad händer då om man tilldelar en klass värdet av en annan klass? Faktum är att även detta fungerar. Vi kommer också strax att se hur vi kan anpassa klassens reaktion på tilldelningsoperatorn. Dessutom kommer vi så småningom att titta hur man kan anpassa olika operatorer i kapitlet ‘Överlagrade operatorer’.

 

Låt oss prova att tilldela ett objekt av klassen CText till ett annat objekt:

 

CText Text1;

CText Text2(”Här är texten!”);

 

Text1 = Text2;

 

Detta ser vi första ögonkast korrekt ut, men vad har vi egentligen gjort? Vi har kopierat de värden som finns i Text2:s medlemmar till Text1:s medlemmar. En av dessa medlemmar är pekaren m_pText. Skulle vi be Text1 att skriva ut sitt innehåll skulle inte ens det visa att vi gjort fel. Text1 skulle nämligen skriva ut texten som finns i Text2, eftersom Text1.m_pText nu pekar på den.

 

Vi har dessutom tappat bort adressen till Text1:s ursprungliga buffert, och kan inte frigöra det minnet. Skulle vi nu frigöra minnet i bägge objekten, vilket också sker när vi kommer till slutet i main, så gör vi faktiskt ‘delete’ två gånger på samma minne. Det var ju inte tillåtet. Det är nog bäst att starta om Windows i ett sådant här läge.


Överlagring av tilldelningsoperatorn.

 

Hur kan det då komma sig att man kan använda tilldelningsoperatorn för att kopiera en hel struktur? När C var ungt gick det inte. Detta är en av de förbättringar som kommit genom åren, och nu går det bra både i C och C++.

 

Hemligheten ligger i nyckelordet ‘operator’. Efter införd förbättring ser egentligen en tilldelning i C ut så här:

 

<variabel 1>.operator=(<variabel 2>);

 

Detta gäller oavsett typ på variabel 1 och 2. De måste naturligtvis vara av samma eller kompatibla typer, men det går t.ex. även med strukturer, som sagt. Det finns nu en förkortning för detta, och den känner vi ju igen:

 

<variabel 1> = <variabel 2>;

 

Den vanliga tilldelningen. Dessa uttryck tolkas dock identiskt av kompilatorn, och vi ska naturligtvis använda den sistnämnda.

 

I och med att det går att skriva på det förstnämnda sätte, så kan det ju faktiskt uttryckas som en funktion. Då kan vi byta ut operatorn på samma sätt som vi bytte ut en medlemsfunktion i förra kapitlet. Detta kallas för ‘operatoröverlag­ring’.

 

Låt oss lägga till en funktion i vår klass CText. Denna funktion ska överlagra tilldelningsoperatorn, och ta emot adressen till en annan text, den som vi vill kopiera m.h.a. tilldelningsoperatorn. Prototypen ska se ut så här:

 

void operator=(const CText &intext);

 

När vi deklarerar funktionen ska vi alltså skriva den kod som gör det vi vill att operatorn ska utföra, d.v.s. kopiera in den text som finns vid &intext till vår interna buffert. Naturligtvis måste vi först ta bort vår gamla buffert och allokera en ny, den nya texten kan ju vara längre än den gamla (precis som vi gjorde med Append()). Så här kan det se ut:

 

void CText::operator=(const CText &intext)

{

    m_Langd = intext.m_Langd;

    delete [] m_pText;

    m_pText = new char[m_Langd + 1];

    strcpy(m_pText, intext.m_pText);

}

 

Observera att Intext är ett objekt av klassen CText. När vi nu befinner oss i ett likadant sammanhang har vi rätt offset för att nå det främmande objektets privata medlemmen m_Langd, och att kompilatorn godkänner detta.

 

Övningsuppgift:

·      Lägg till ovanstående operatoröverlagring i klassen CText.

·      Prova från main(). Skapa tre objekt. Ett som heter Original och innehåller texten ‘Original’, ett som heter Kopia, med texten ‘Kopia’ samt ett tomt, som heter Tomt.

·      Tilldela Kopia värdet av Original.

·      Tilldela Tomt värdet av Original.

·      Använd Append() för att lägga till ” - 1” till objektet Original, ” - 2” till Kopia samt ” - 3” till Tomt, så att man kan se i en utskrift att det är olika objekt.

·      Anropa Skriv för alla tre objekten.

·      Så här ska det se ut:

 

                

 


this.

 

Detta är en speciell pekare som är tillgänglig för medlemsfunktioner. Som nämnts tidigare finns bara en kopia av en klass metoder (medlemsfunktioner) i minnet, även om flera objekt skapats. När en metod är aktiv har den tillgång till pekaren ‘this’, och den pekar på just det objekt som aktiverat funktionen.

 

Observera att statiska metoder inte har tillgång till ‘this’-pekaren. Vi kommer att titta mer på statiska metoder i ett kommande avsnitt.

 

Man behöver inte använda ‘this’ som en pekare när man använder medlems­variabler, det har vi ju inte gjort på hela tiden. I CCirkels accessfunktion GetRadie() skulle vi kunna skriva på tre olika sätt, vilka alla ger samma resultat:

 

return m_Radie;         // Använder automatiskt ‘this’

return this->m_Radie;   // Exakt vad ovanstående blir.

return (*this).m_Radie; // Använder ‘this’ som objekt tillsam-

                           mans med elementoperatorn.

 

Varför behöver vi då ‘this’? Som vanligt i C/C++ kan det uppstå ‘situationer’. En av dessa situationer har vi faktiskt just seglat in i, när vi genomförde det senaste steget i vårt exempel med klassen CText ovan. Vad skulle hända om man skrev följande:

 

Original = Original;

 

Det ser oskyldigt ut väl? Objektet ‘Original’ innehåller texten ‘Original’, och det gör den väl även efter ovanstående rad?

 

Nej, titta i vår operatoröverlagring! Först tar vi bort textbuffern m.h.a. ‘delete’, sedan allokerar vi en ny m.h.a. ‘new’. Om vi inte har tur nog att få tillbaks sam­ma minne (räkna inte med det), så blir det nog något annat. Därefter har vi två pekare som pekar på denna plats. Den adress vi får in via ‘&intext’ är ju samma som redan finns i m_pText, eftersom det är samma objekt. Sen kopierar vi det skräp som står på den adress bägge m_pText pekar på, till sig själv.

 

Inte duktig alltså. Men vem skulle kunna skriva nåt så dumt som ovanstående självtilldelning? Den är ju inte särskilt användbar. Nej, men som spelmannen sa: ‘Det är mänskligt att fela.’ Vi skulle kunna trixa in oss bland pekare etc. så att t.ex. följande situation uppstår:

 

CText *p1 = &Original;

...

// Tvåtusen rader och hundra koppar kaffe senare:

...

Original = *p1;

Skulle sedan detta blandas in i länkade listor etc. kanske även du kan göra ett misstag. Låt oss därför skydda klassen mot självtilldelning. Det är här som ‘this’ kommer som en räddare i nöden. Om vårt objekt, ‘this’, är det samma som vi får adressen till, ‘&intext’, så gör vi ingenting alls:

 

void CText::operator=(const CText &intext)

{

    if(&intext != this)

    {

        m_Langd = intext.m_Langd;

        delete [] m_pText;

        m_pText = new char[m_Langd + 1];

        strcpy(m_pText, intext.m_pText);

    }

}

 

I C/C++ har ett uttryck ett värde. Det är därför vi kan koppla flera tilldelningar. I nedanstående sats får variablerna a, b och c alla värdet 5:

 

a = b = c = 5;

 

Eller hur? Tilldelningsoperatorn är alltså högerprioriterad. Kompilatorn läser från vänster: a = ‘nåt som först måste beräknas’ etc. Den skjuter upp tilldelning­en tills den nästlat sig längst till höger, vilket ger den ett fast värde, 5. Därefter återvänder den steg för steg och löser alla tilldelningar. Detta gäller även om uttrycket innehåller funktionsanrop, autoincrement (++/--) etc.

 

Vad händer då om vi skriver:

 

CText Original(”Original”);

CText Kopia(”Kopia”);

CText Tomt;

 

Tomt = Kopia = Original;

 

Detta skulla innebära att vi i samband med användning av tilldelningsoperatorn förväntade oss något slags returvärde att vidarepassa till nästa tilldelning. Be­tänk att kompilatorn gör den högra tilldelningen ovan. Den ger den överlagrade

tilldelningsoperatorn adressen till objektet ‘Original’. Sedan ska den tilldel­ningens värde användas vid nästa värde, men vi har inte försett kompilatorn med något värde. Vi kan heller inte förvänta oss att kompilatorn tar adressen till ‘Kopia’, eftersom det objektet redan är använt. Vi måste alltså låta vår överlag­rade operator returnera en referens till objektet ‘Kopia’ för att uttrycket ”Kopia = Original” ska få ett värde. Det är en funktion i ‘Kopia’ som körs, alltså kan den returnera ‘*this’. Om vi säger att den överlagrade operatorn är av typen ‘referens till CText’ (CText&), kommer ‘return *this’ attförorsaka att kompilatorn skapar ett temporärt objekt som överlever vårt objekt, och därigenom kan tilldelas av anropande funktion. Vi lägger till denna rad sist i funktionen för vår överlagrade operator:

 

    return *this;

 

Naturligtvis måste vi då ändra funktionens typ. Det mest effektiva i detta sammanhang är att använda en referens. Slutgiltiga versionen av vår överlagrade operator blir således:

 

CText &CText::operator=(const CText &intext)

{

    if(&intext != this)

    {

        m_Langd = intext.m_Langd;

        delete [] m_pText;

        m_pText = new char[m_Langd + 1];

        strcpy(m_pText, intext.m_pText);

    }

    return *this;

}

 

Detta är för övrigt den teknik som användes i klasserna istream och ostream för att möjliggöra inmatning/utskrift av flera variabler och konstanter i samma sats:

 

cin >> Namn >> Vikt >> Langd;

...

cout << ”Hör nu här ” << Namn << ”, jag tycker att ”

     << Vikt << ”Kg är lagom för din längd!\n”;

 

I äldre versioner av C++ var ‘this’ inte deklarerad som const. Det innebar att man kunde ändra värdet i den. Detta rekommenderades absolut inte, och de som ändå gjorde detta (och antagligen ställde till värre röra än döden kräver) kan inte kompilera dessa program med moderna versioner av C++ kompilatorn.

 

Övningsuppgift:

·      Lägg till ovanstående ändringar i klassen CText:s överlagrade operator.

·      Ändra tilldelningarna i main så att de lyder:

 

Tomt = Kopia = Original;

 

·      Testa programmet. Utskriften ska bli samma som förut.


Kopieringskonstruktorn.

 

I det tidigare exemplet skapade vi ett tomt objekt och tilldelade det värdet av ett annat objekt, vilket innehöll texten ‘Original’:

 

CText Original(”Original”);

CText Tomt;

...

Tomt = Original; // Vi hoppar över ‘Kopia’ i detta exempel.

 

I detta fall har vi gjort en tilldelning. Hur skulle det se ut om vi initierade ‘Tomt’ med objektet ‘Original’ i stället? Borde detta inte ge samma resultat:

 

CText Original(”Original”);

CText Tomt(Original);

 

Som CText nu är skrivet kommer kompilatorn att kopiera in medlemmarna från ‘Original’ till ‘Tomt’ en i taget, och det kommer ju som vi tidigare sett att skapa ett nytt objekt som innehåller en pekare till textbuffern i det andra objektet. Inge bra alltså. Vi behöver alltså skapa en speciell konstruktor som aktiveras i detta fall. Det är inga problem, vi bara överlagrar konstruktorn en gång till. Para­meterlistan ska i detta fall ta en referens till ett CText-objekt som argument:

 

CText::CText(const CText &intext);

 

Funktionskoden liknar i stort sett den vi hade vid operatoröverlagringen, den ut­för ju samma uppgift:

 

CText::CText(const CText &intext)

{

    m_Langd = intext.m_Langd;

    m_pText = new char[m_Langd + 1];

    strcpy(m_pText, intext.m_pText);

}

 

Skillnaden ligger i att vi:

1.   Inte riskerar självtilldelning, behöver inte kolla ‘new’.

2.   Inte har någon gammal buffer att tömma, detta är ju en konstruktor.

3.   Inte får returnera något i en konstruktor.

 

Övningsuppgift:

·      Lägg till ovanstående ‘copy constructor’ i klassen CText.

·      Testa tilldelning med nedanstående sats i main():

        

         CText Original(”Original”);

         CText Kopia(Original);

        

·      Vid utskrift ska objektet ‘Kopia’ innehålla texten ”Original”.


Objekt som argument/returvärde.

 

Det finns två situationer då kopieringskonstruktorn, ‘copy constructor’ anropas, förutom vid deklaration:

 

1.   När en funktion tar ett objekt som argument.

2.   När en funktion returnerar ett objekt.

 

Vid funktionsanrop lägger kompilatorn variabler på stacken. Den anropade funktionen får en kopia på data som finns i den anropande funktionen. När det gäller strukturvariabler skapar kompilatorn en temporär struktur åt den anrop­ade funktionen, och kopierar in alla medlemmar en och en, för att till sist lägga adressen till strukturen på stacken.

 

När det gäller klasser sker samma sak, men dessutom används kopieringskon­struktorn, vilket ligger naturligt till hands. Finns ingen sådan sker kopiering enligt tidigare, vilket ju inte var så bra om man använde pekare till data. I så fall skulle man ju peka på originaldata, och inte det temporära data som avses.

 

En annan synnerligen allvarlig konsekvens vore att den lokala kopian i den anropade funktionen kommer att anropa sin destruktor när funktionen lämnar sitt sammanhang. Då utförs ‘delete’ på den text som finns i minnet, vilket den anropande funktionen inte är klar med än. Så småningom kommer den anropan­de funktionen också att lämna sitt sammanhang, varvid den försöker använda ‘delete’ på samma adress, och då vet vi ju vad som väntar...

 

Samma problem skulle uppstå även när man använde ett objekt som returvärde. När funktionen kommer till ‘return <object>;’ lämnar den sitt sammanhang, och objektet förstörs. Den anropande funktionen får adressen till en text som just tagits bort, ‘delete’. Skriver man ett funktionsanrop som tilldelar ett objekt funtionsanropets värde så anropas kopieringskonstruktorn för att initiera det objekt som kommer att höra till den anropande funktionen.

 

Skriv därför alltid en kopieringskonstruktor och en överlagrad tlldelningsopera­tor till varje klass som innehåller pekare vilka pekar på data som allokeras dynamiskt.

 


Referenser till objekt som argument/returvärde.

 

I vissa situationer behöver man egentligen inte en egen kopia på ett objekt i en anropande funktion. Vi vill kanske inte förändra data, bara titta litet. Då är det egentligen onödigt att skapa objektet och köra kopieringskonstruktorn etc. Man klarar sig lika bra med originalobjektet. Detta kan vi naturligtvis göra genom att skicka en referens till objektet i stället, så använder den anropade funktionen samma objekt som den anropande.

 

Observera att vi som parameter har en referens till ett objekt som är konstant i och med att vi angivit nyckelordet ‘const’ i ett funktionshuvud:

                

CText::CText(const CText &intext);

 

Detta förhindrar anrop till funktioner som kan ändra data d.v.s funktioner som anropas för ett konstant objekt måste vara deklarerade som konstanta. Vi ‘lovar’ på detta sätt att vi inte kommer att förändra datat i ‘objektet intext’. Se avsnittet ‘Konstanta objekt’ i föregående kapitel.

 

Om kopieringskontruktorn istället för en referens tog ett objekt som argument skulle den anropa sig själv för att initiera detta objekt, och vi fick en evig rekursion.

 

Det kan också vara mer effektivt att returnera en referens i stället för ett objekt. Tänk bara på att kompilatorn håller reda på er temporära referens ‘*this’, men returnera inte andra variabler vilka går ur sitt sammanhang med den anropade funktionen. Dessa pekar i så fall på otillåtet minne. Samma sak gäller för pekare.